using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;

namespace LS.Digitize
{
    public class JsonReader: Reader
    {
        private const char EndOfStreamChar = (char)65535;
        protected StreamReader m_StreamReader;

        public bool ReadFailure { get; protected set; } = false;

        public JsonReader(Stream stream)
        {
            m_StreamReader = new StreamReader(stream);
            StartReadFile();
        }
        public override short ReadShort()
        {
            return (short)int.Parse(GetValueString());
        }

        public override ushort ReadUshort()
        {
            return (ushort)int.Parse(GetValueString());
        }

        public override uint ReadUint()
        {
            return uint.Parse(GetValueString());
        }

        public override string ReadString()
        {
            if(ReadQuotationMarkOrNullIgnoreWhitespace())
                return null;
            char c;

            StringBuilder sb = new StringBuilder();

            while(!IsQuotationMark((c = (char)m_StreamReader.Read())))
            {
                if(c == '\\')
                {
                    c = (char)m_StreamReader.Read();
                    if(IsEndOfStream(c))
                        throw new FormatException("Reached end of stream while trying to read string literal.");

                    switch(c)
                    {
                        case 'b':
                            c = '\b';
                            break;
                        case 'f':
                            c = '\f';
                            break;
                        case 'n':
                            c = '\n';
                            break;
                        case 'r':
                            c = '\r';
                            break;
                        case 't':
                            c = '\t';
                            break;
                        default:
                            break;
                    }
                }
                sb.Append(c);
            }
            return sb.ToString();
        }
        
        public override int ReadInt()
        {
            return int.Parse(GetValueString());
        }

        public override long ReadLong()
        {
            return long.Parse(GetValueString());
        }

        public override ulong ReadUlong()
        {
            return ulong.Parse(GetValueString());
        }

        public override byte ReadByte()
        {
            return (byte)int.Parse(GetValueString());
        }

        public override sbyte ReadSbyte()
        {
            return (sbyte)int.Parse(GetValueString());
        }

        public override float ReadFloat()
        {
            return float.Parse(GetValueString(), CultureInfo.InvariantCulture);
        }

        public override double ReadDouble()
        {
            return double.Parse(GetValueString(), CultureInfo.InvariantCulture);
        }

        public override bool ReadBool()
        {
            return bool.Parse(GetValueString());
        }

        public override char ReadChar()
        {
            return char.Parse(GetValueString());
        }

        public override decimal ReadDecimal()
        {
            return decimal.Parse(GetValueString(), CultureInfo.InvariantCulture);
        }

        public override byte[] ReadByteArray()
        {
            return System.Convert.FromBase64String(ReadString());
        }

        public override string ReadPropertyName()
        {
            char c = PeekCharIgnoreWhitespace();

            // Check whether there are any properties left to read.
            if(IsTerminator(c))
                return null;
            else if(c == ',')
                ReadCharIgnoreWhitespace();
            else if(!IsQuotationMark(c))
                throw new FormatException("Expected ',' separating properties or '\"' before property name, found '"+c+"'.");

            var propertyName = ReadString();
            if(propertyName == null)
                throw new FormatException("Stream isn't positioned before a property.");

            // Skip the ':' seperating property and value.
            ReadCharIgnoreWhitespace(':');

            return propertyName;
        }

        public override byte[] ReadElement()
        {
			StreamWriter writer = new StreamWriter(new MemoryStream(2048));

			using(writer)
			{
				int nesting = 0;
				char c = (char)m_StreamReader.Peek();

				if(!IsOpeningBrace(c))
				{
					if(c == '\"')
					{
						ReadOrSkipChar(writer, false);
						ReadOrSkipString(writer, false);
					}
					else
						while(!IsEndOfValue((char)m_StreamReader.Peek()))
							ReadOrSkipChar(writer, false);

					writer.Flush();
					return ((MemoryStream)writer.BaseStream).ToArray();
				}

				while(true)
				{
					c = ReadOrSkipChar(writer, false);

					if(c == EndOfStreamChar)
						throw new FormatException("Missing closing brace detected, as end of stream was reached before finding it.");

					if(IsQuotationMark(c))
					{
                        ReadOrSkipString(writer, false);
						continue;
					}

					switch(c)
					{
						case '{':
						case '[': 
							nesting++;
							break;
                        case '}':
						case ']':
							nesting--;
							if(nesting<1)
							{
								writer.Flush();
								return ((MemoryStream)writer.BaseStream).ToArray();
							}
							break;
						default:
							break;
					}
				}
			}
        }

        public override bool CouldBeNull()
        {
            return PeekCharIgnoreWhitespace() == 'n';
        }

        public override void ReadNull()
        {
            GetValueString();
        }
        
        public override void SkipElement()
        {
			int nesting = 0;
			char c = (char)m_StreamReader.Peek();

			// Determine if we're skipping a primitive type.
			// First check if it's an opening object or array brace.
			if(!IsOpeningBrace(c))
			{
				// If we're skipping a string, use SkipString().
				if(c == '\"')
				{
					// Skip initial quotation mark as SkipString() requires this.
					ReadOrSkipChar(null, true);
					ReadOrSkipString(null, true);
				}
				// Else we just need to read until we reach a closing brace.
				else
					// While we've not peeked a closing brace.
					while(!IsEndOfValue((char)m_StreamReader.Peek()))
						ReadOrSkipChar(null, true);
                return;
			}

			// Else, we're skipping a type surrounded by braces.
			// Iterate through every character, logging nesting.
			while(true)
			{
				c = ReadOrSkipChar(null, true);

				if(c == EndOfStreamChar) // Detect premature end of stream, which denotes missing closing brace.
					throw new FormatException("Missing closing brace detected, as end of stream was reached before finding it.");

				// Handle quoted strings.
				// According to the RFC, only '\' and '"' must be escaped in strings.
				if(IsQuotationMark(c))
				{
                    ReadOrSkipString(null, true);
					continue;
				}

				// Handle braces and other characters.
				switch(c)
				{
					case '{': // Entered another level of nesting.
					case '[': 
						nesting++;
						break;
                    case '}': // Exited a level of nesting.
					case ']':
						nesting--;
						// If nesting < 1, we've come to the end of the object.
						if(nesting<1)
						    return;
						break;
					default:
						break;
				}
			}
        }
        
        private void ReadOrSkipString(StreamWriter writer, bool skip = false)
        {
            bool endOfString = false;
            // Read to end of string, or throw error if we reach end of stream.
            while(!endOfString)
            {
                char c = ReadOrSkipChar(writer, skip);
                switch(c)
                {
                    case EndOfStreamChar:
                        throw new FormatException("String without closing quotation mark detected.");
                    case '\\':
                        ReadOrSkipChar(writer, skip);
                        break;
                    default:
                        if(IsQuotationMark(c))
                            endOfString = true;
                        break;
                }
            }
        }
        
        private char ReadOrSkipChar(StreamWriter writer, bool skip)
        {
            char c = (char)m_StreamReader.Read();
            if(!skip) writer.Write(c);
            return c;
        }
        
        public override void Dispose()
        {
            m_StreamReader.Dispose();
            if(!ReadFailure)
                EndReadFile();
        }

        public override bool StartReadFile()
        {
            char firstChar = ReadCharIgnoreWhitespace();
            if (firstChar != '{') // If first char isn't '{', it's not valid JSON.
            {
                Debug.Log(new FormatException("File is not valid JSON. Expected '{' at beginning of file, but found '"+firstChar+"'."));
                ReadFailure = true;
                return false;
            }

            return true;
        }

        public override void EndReadFile()
        {
            
        }

        public override bool StartReadDictionary()
        {
            return StartReadObject();
        }

        public override void EndReadDictionary()
        {
            EndReadObject();
        }

        public override bool StartReadDictionaryKey()
        {
            char c =  PeekCharIgnoreWhitespace();
            return c != '}' && c != ',';
        }

        public override void EndReadDictionaryKey()
        {
            ReadCharIgnoreWhitespace(':');
        }

        public override bool StartReadDictionaryValue()
        {
            char c = PeekCharIgnoreWhitespace();
            return c != '}' && c != ',';
        }

        public override void EndReadDictionaryValue()
        {
            char c = PeekCharIgnoreWhitespace(',', '}');
            if (c == ',')
                ReadCharIgnoreWhitespace();
        }

        public override bool StartReadCollection()
        {
            char c = PeekCharIgnoreWhitespace();
            if (c == '[')
            {
                ReadCharIgnoreWhitespace();
                serializationDepth++;
                return true;
            }
            else if (CouldBeNull())
            {
                return true;
            }
            return false;
        }

        public override void EndReadCollection()
        {
            char c = PeekCharIgnoreWhitespace(']', ',');
            if (c == ']')
            {
                ReadCharIgnoreWhitespace();
                serializationDepth--;
                c = PeekCharIgnoreWhitespace();
            }
            
            if(c == ',')
            {
                ReadCharIgnoreWhitespace();
            }
        }

        public override bool StartReadCollectionItem()
        {
            char c = PeekCharIgnoreWhitespace();
            return c != '}' && c != ',';
        }

        public override void EndReadCollectionItem()
        {
            char c = PeekCharIgnoreWhitespace(',', ']');
            if (c == ',')
                ReadCharIgnoreWhitespace();
        }

        /// <summary>
        /// 如果是括号开头或者n开头，返回true。n开头可能为null，需要在之后再次判断并读取null字符串。
        /// </summary>
        /// <returns></returns>
        public override bool StartReadObject()
        {
            char c = PeekCharIgnoreWhitespace();
            if (c == '{')
            {
                ReadCharIgnoreWhitespace();
                serializationDepth++;
                return true;
            }
            else if (CouldBeNull())
            {
                return true;
            }
            return false;
        }

        
        public override void EndReadObject()
        {
            char c = PeekCharIgnoreWhitespace('}', ',');
            if (c == '}')
            {
                ReadCharIgnoreWhitespace();
                serializationDepth--;
                c = PeekCharIgnoreWhitespace();
            }

            if (c == ',')
                ReadCharIgnoreWhitespace();
        }
        
        private char ReadCharIgnoreWhitespace(bool ignoreTrailingWhitespace=true)
        {
            char c;
            // Skip leading whitespace and read char.
            while(IsWhiteSpace(c = (char)m_StreamReader.Read()))
            {}

            // Skip trailing whitespace.
            if(ignoreTrailingWhitespace)
                while(IsWhiteSpace((char)m_StreamReader.Peek()))
                    m_StreamReader.Read();

            return c;
        }

        private bool ReadNullOrCharIgnoreWhitespace(params char[] expectedChar)
        {
            char c = ReadCharIgnoreWhitespace();

            // Check for null
            if(c == 'n')
            {
                var chars = new char[3];
                m_StreamReader.ReadBlock(chars, 0, 3);
                if((char)chars[0] == 'u' && (char)chars[1] == 'l' && (char)chars[2] == 'l')
                    return true;
            }

            if(!expectedChar.Contains(c))
            {
                string expected = string.Join(",", expectedChar);
                if(c == EndOfStreamChar)
                    throw new FormatException("End of stream reached when expecting '"+expected+"'.");
                else
                    throw new FormatException("Expected \'"+expected+"\' or \"null\", found \'"+c+"\'.");
            }
            return false;
        }

        /*
         * 	Reads a char from the stream and ignores leading and trailing whitespace.
         * 	Throws an error if the char isn't equal to the one specificed as a parameter, or if it's the end of stream.
         */
        private char ReadCharIgnoreWhitespace(params char[] expectedChar)
        {
            char c = ReadCharIgnoreWhitespace();
            
            if(!expectedChar.Contains(c))
            {
                string expected = string.Join(",", expectedChar);
                if(c == EndOfStreamChar)
                    throw new FormatException("End of stream reached when expecting '"+expected+"'.");
                else
                    throw new FormatException("Expected \'"+expected+"\', found \'"+c+"\'.");
            }
            return c;
        }
        
        private bool ReadQuotationMarkOrNullIgnoreWhitespace()
        {
            char c = ReadCharIgnoreWhitespace(false); // Don't read trailing whitespace as this is the value.

            if(c == 'n')
            {
                var chars = new char[3];
                m_StreamReader.ReadBlock(chars, 0, 3);
                if((char)chars[0] == 'u' && (char)chars[1] == 'l' && (char)chars[2] == 'l')
                    return true;
            }
            else if(!IsQuotationMark(c))
            {
                if(c == EndOfStreamChar)
                    throw new FormatException("End of stream reached when expecting quotation mark.");
                else
                    throw new FormatException("Expected quotation mark, found \'"+c+"\'.");
            }
            return false;
        }
        
        private void ReadWhiteCount(int count)
        {
            char[] arr = new char[count];
            m_StreamReader.ReadBlock(arr, 0, count);
        }
        
        private char PeekCharIgnoreWhitespace(params char[] expectedChar)
        {
            char c = PeekCharIgnoreWhitespace();
            if(!expectedChar.Contains(c))
            {
                string expected = string.Join(",", expectedChar);
                if(c == EndOfStreamChar)
                    throw new FormatException("End of stream reached while peeking, when expecting '"+expected+"'.");
                else
                    throw new FormatException("Expected \'"+expected+"\', found \'"+c+"\'.");
            }
            return c;
        }
        
        /*
         * 	Peeks the next char in the stream, ignoring leading whitespace, but not trailing whitespace.
         *	Throws an error if the char isn't equal to the one specificed as a parameter.
         */
        private char PeekCharIgnoreWhitespace()
        {
            char c;
            // Skip leading whitespace and read char.
            while(IsWhiteSpace(c = (char)m_StreamReader.Peek()))
                m_StreamReader.Read();
            return c;
        }

        // Skips all whitespace immediately after the current position.
        private void SkipWhiteSpace()
        {
            while(IsWhiteSpace((char)m_StreamReader.Peek()))
                m_StreamReader.Read();
        }

        private void SkipOpeningBraceOfFile()
        {
            // Skip the whitespace and '{' at the beginning of the JSON file.
            char firstChar = ReadCharIgnoreWhitespace();
            if(firstChar != '{') // If first char isn't '{', it's not valid JSON.
                throw new FormatException("File is not valid JSON. Expected '{' at beginning of file, but found '"+firstChar+"'.");
        }

        private void SkipWhiteCount(int count)
        {
            for (int i = 0; i < count; i++)
            {
                m_StreamReader.Read();
            }
        }
        
        private static bool IsWhiteSpace(char c)
        {
            return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
        }

        private static bool IsOpeningBrace(char c)
        {
            return (c == '{' || c == '[');
        }

        private static bool IsEndOfValue(char c)
        {
            return (c == '}' || c == ' ' || c == '\t' || c == ']' || c == ',' || c== ':' || c == EndOfStreamChar || c == '\n' || c == '\r');
        }

        private static bool IsTerminator(char c)
        {
            return (c == '}' || c == ']');
        }

        private static bool IsQuotationMark(char c)
        {
            return c == '\"' || c == '“' || c == '”';
        }

        private static bool IsEndOfStream(char c)
        {
            return c == EndOfStreamChar;
        }
        
        private string GetValueString()
        {
            StringBuilder builder = new StringBuilder();

            while(!IsEndOfValue(PeekCharIgnoreWhitespace()))
                builder.Append((char)m_StreamReader.Read());

            // If it's an empty value, return null.
            if(builder.Length == 0)
                return null;
            return builder.ToString();
        }
    }
}